# -*- coding: utf-8 -*-

from .op_code import *
from .value import Value, NumberValue

STACK_LIMIT = 512

binary_ops = [
    OP_ADD, OP_SUB, OP_MUL, OP_DIV
]

class VM:
    def __init__(self):
        self.sp = 0

        self.constants = []
        self.stack = [None] * STACK_LIMIT

    def exec(self, program):
        # ast = parser.parse(program)
        # code = compiler.compile(ast)

        self.constants.append(NumberValue(2))
        self.constants.append(NumberValue(3))

        self.codes = [
            OP_CONST, 0,
            OP_CONST, 1,
            OP_ADD,
            OP_HALT,
        ]

        self.ip = 0
        return self.eval()

    def eval(self):
        while True:
            opcode = self.read_byte()
            if opcode == OP_HALT:
                return self.pop()
            elif opcode == OP_CONST:
                const_index = self.read_byte()
                constant = self.constants[const_index]
                self.push(constant)
            elif opcode in binary_ops:
                op2 = self.pop().as_number()
                op1 = self.pop().as_number()
                result = self.handle_binary_op(opcode, op1, op2)
                self.push(NumberValue(result))
            else:
                raise ValueError('Unknown opcode: 0x{:02x}'.format(opcode))

    def read_byte(self):
        rv = self.codes[self.ip]
        self.ip += 1
        return rv

    def push(self, value: Value):
        if self.sp >= STACK_LIMIT:
            raise OverflowError('push(): stack overflow.')
        self.stack[self.sp] = value
        self.sp += 1

    def pop(self):
        if self.sp == 0:
            raise IndexError('pop(): empty stack.')
        self.sp -= 1
        return self.stack[self.sp]

    def handle_binary_op(self, opcode, op1, op2):
        if opcode == OP_ADD:
            return op1 + op2
        elif opcode == OP_SUB:
            return op1 + op2
        elif opcode == OP_MUL:
            return op1 * op2
        elif opcode == OP_DIV:
            return op1 / op2
        else:
            raise Exception('Unknown binary op: 0x{:02x}'.format(opcode))
